{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "40d17b07-94b3-4d13-a4db-549608fc28f9",
   "metadata": {},
   "source": [
    "# Embedding  \n",
    "在深度学习中，\"embedding\"（嵌入）是一种将类别型数据（如单词、短语或整个文档）转换为实数向量的技术。这些向量通常在多维空间中表示，且每个维度代表不同的特征或属性。Embedding的目的是捕捉和表示数据项之间的相似性和差异性，使得具有相似含义或特征的数据项在向量空间中彼此接近。通俗的讲，模型不能直接处理单词，而需要将单词转为一种机器可以理解的表达方式，这就是生成embedding的过程。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "68fd8358-6e0e-4161-b44c-d4d50eff51b2",
   "metadata": {},
   "source": [
    "## 为什么使用Embedding？  \n",
    "* **降维**：Embedding可以将高维的稀疏数据（如one-hot编码的向量）转换为低维的密集数据，这有助于减少模型的参数数量和计算复杂度。\n",
    "* **捕捉语义信息**：通过训练，embedding可以学习到数据项之间的语义关系，例如在自然语言处理中，相似的单词会被映射到向量空间中彼此接近的位置。\n",
    "* **泛化能力**：Embedding能够捕捉到数据项之间的模式和关系，从而提高模型的泛化能力"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2992f6c9-3ea5-4568-a438-aec23f689bcd",
   "metadata": {},
   "source": [
    "## 如何创建Embedding？  \n",
    "Embedding通常是通过无监督学习或监督学习的方式从数据中学习得到的。以下是一些常见的方法：\n",
    "* **Word2Vec**：这是一种流行的无监督学习方法，通过预测目标单词的上下文（或相反）来学习单词的向量表示。\n",
    "* **GloVe（Global Vectors for Word Representation）**：类似于Word2Vec，GloVe是基于单词共现矩阵的统计信息来学习词向量的。\n",
    "* **FastText**：这是Word2Vec的一个变体，它通过学习n-gram（而不是单个单词）的表示来捕捉更多的语义信息。\n",
    "* **BERT（Bidirectional Encoder Representations from Transformers）**：这是一种基于Transformer架构的预训练模型，它通过双向上下文来学习文本的嵌入表示。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "97bed8e0-0526-47aa-a477-87ed85d047d4",
   "metadata": {},
   "source": [
    "## Embedding的应用\n",
    "Embedding在深度学习中有着广泛的应用，尤其是在自然语言处理（NLP）领域：\n",
    "* **文本分类**：使用文本的Embedding作为输入特征，可以提高文本分类任务的性能。\n",
    "* **情感分析**：通过嵌入表示文本，可以更好地捕捉到文本中的情感倾向。\n",
    "* **机器翻译**：在机器翻译任务中，Embedding可以帮助模型理解和生成不同语言的文本。\n",
    "* **推荐系统**：Embedding可以用于表示用户和物品的特征，从而提高推荐系统的准确性。  \n",
    "\n",
    "总的来说，embedding是深度学习中一种强大的技术，它通过将离散数据转换为连续的向量表示，使得模型能够捕捉到更丰富的数据特征和关系。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d588a022-cb2a-4c46-b627-f830cc02dd53",
   "metadata": {},
   "source": [
    "## 实例"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "994bb842-ee4e-4847-8181-cdf86dd1c152",
   "metadata": {},
   "source": [
    "### 1. Bag of Words\n",
    "\n",
    "词袋模型（Bag of Words, BoW）是一种简单而强大的文本表示方法，用于自然语言处理和信息检索任务。它将文本内容转换为单词的出现次数，而不考虑单词之间的顺序。以下是词袋模型的基本概念和使用Python中的`sklearn`库实现的示例代码。\n",
    "\n",
    "### 基本概念：\n",
    "\n",
    "* **分词（Tokenization）**：将文本分割成单词或词汇单元。\n",
    "* **构建词汇表（Vocabulary）**：从所有文档中提取唯一的单词列表。\n",
    "* **文本向量化**：将每个文档转换为一个数值向量，向量的长度是词汇表的大小，每个元素代表一个单词在文档中的出现次数或是一个布尔值，指示单词是否出现。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8ce7b14b-f594-4206-a977-588a1fa32e23",
   "metadata": {},
   "source": [
    "### 示例代码：  \n",
    "假设我们有以下三篇文档："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "e1818365-cd4c-4307-95b5-7125f25cd1d2",
   "metadata": {},
   "outputs": [],
   "source": [
    "docs = [\n",
    "    'The sky is blue.',\n",
    "    'The sun is bright.',\n",
    "    'The sun in the sky is bright.'\n",
    "]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4930e062-fd04-4653-8697-b073c3a42762",
   "metadata": {},
   "source": [
    "我们将执行以下步骤来创建一个词袋模型：\n",
    "1. 分词和构建词汇表。\n",
    "2. 文本向量化。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "6f217753-842c-4e9f-afaf-8f66382e6244",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Vocabulary: {'the': 6, 'sky': 4, 'is': 3, 'blue': 0, 'sun': 5, 'bright': 1, 'in': 2}\n",
      "Document-term matrix:\n",
      " [[1 0 0 1 1 0 1]\n",
      " [0 1 0 1 0 1 1]\n",
      " [0 1 1 1 1 1 2]]\n"
     ]
    }
   ],
   "source": [
    "from sklearn.feature_extraction.text import CountVectorizer\n",
    "\n",
    "# 初始化CountVectorizer\n",
    "vectorizer = CountVectorizer()\n",
    "\n",
    "# 拟合和转换文档\n",
    "X = vectorizer.fit_transform(docs)\n",
    "\n",
    "# 查看词汇表\n",
    "vocabulary = vectorizer.vocabulary_\n",
    "print(\"Vocabulary:\", vocabulary)\n",
    "\n",
    "# 查看文档的词袋表示\n",
    "print(\"Document-term matrix:\\n\", X.toarray())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f1250d24-92e0-4157-821b-fcd43d77eeef",
   "metadata": {},
   "source": [
    "- `Vocabulary`：输出词汇表，它是一个字典，将每个单词映射到一个唯一的索引。\n",
    "- `Document-term matrix`：输出每个文档的词袋表示，这是一个矩阵，其中的行对应文档，列对应词汇表中的单词。矩阵中的每个元素是该单词在文档中的出现次数。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "068c3bf9-2dbc-42a5-b615-ffd4fb14a712",
   "metadata": {},
   "source": [
    "### 2. TF-IDF  \n",
    "TF-IDF（Term Frequency-Inverse Document Frequency）是一种用于评估一个词语对于一个文档集或一个语料库中的其中一份文档的重要程度的统计方法。它由两部分组成：词频（TF）和逆文档频率（IDF）。\n",
    "### 基本概念：  \n",
    "* **词频（Term Frequency, TF）**：词频是某个词在文档中出现的次数，通常被标准化（词频除以文档中词语总数），以防止它偏向长文档。\n",
    "* **逆文档频率（Inverse Document Frequency, IDF）**：逆文档频率是文档集中包含该词的文档数量的倒数，对数化处理后得到的权重。IDF反映了一个词在文档集中的罕见程度。  \n",
    "TF-IDF的计算公式为：\n",
    "\n",
    " $TFIDF(t, d, D) = TF(t, d) \\times IDF(t, D)$ \n",
    "\n",
    "其中：\n",
    "\n",
    "- \\( $t$ \\) 是要计算TF-IDF的词语。\n",
    "- \\( $d$ \\) 是目标文档。\n",
    "- \\( $D$ \\) 是整个文档集。\n",
    "- \\( $TF(t, d) $\\) 是词 \\( $t$ \\) 在文档 \\( $d$ \\) 中的词频。\n",
    "- \\( $IDF(t, D)$ \\) 是词 \\( $t$ \\) 在文档集 \\( $D$\\) 中的逆文档频率"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1d4ee8c2-5ffd-430e-ad23-41e67eb4422d",
   "metadata": {},
   "source": [
    "以下是使用Python的`sklearn`库中的`TfidfVectorizer`类来计算TF-IDF的示例代码："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "31c7a0ca-4794-42ce-80fb-1ac34c6f81b4",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Document: Data Science is an overlap between Arts and Science\n",
      "Word: about, TF-IDF: 0.0000\n",
      "Word: aesthetics, TF-IDF: 0.0000\n",
      "Word: an, TF-IDF: 0.3806\n",
      "Word: and, TF-IDF: 0.3001\n",
      "Word: arts, TF-IDF: 0.3001\n",
      "Word: beauty, TF-IDF: 0.0000\n",
      "Word: between, TF-IDF: 0.3806\n",
      "Word: data, TF-IDF: 0.3001\n",
      "Word: extract, TF-IDF: 0.0000\n",
      "Word: from, TF-IDF: 0.0000\n",
      "Word: is, TF-IDF: 0.2430\n",
      "Word: knowledge, TF-IDF: 0.0000\n",
      "Word: natural, TF-IDF: 0.0000\n",
      "Word: overlap, TF-IDF: 0.3806\n",
      "Word: science, TF-IDF: 0.4859\n",
      "Word: studies, TF-IDF: 0.0000\n",
      "Word: the, TF-IDF: 0.0000\n",
      "Word: to, TF-IDF: 0.0000\n",
      "Word: world, TF-IDF: 0.0000\n",
      "\n",
      "Document: Arts studies the beauty and aesthetics\n",
      "Word: about, TF-IDF: 0.0000\n",
      "Word: aesthetics, TF-IDF: 0.4534\n",
      "Word: an, TF-IDF: 0.0000\n",
      "Word: and, TF-IDF: 0.3575\n",
      "Word: arts, TF-IDF: 0.3575\n",
      "Word: beauty, TF-IDF: 0.4534\n",
      "Word: between, TF-IDF: 0.0000\n",
      "Word: data, TF-IDF: 0.0000\n",
      "Word: extract, TF-IDF: 0.0000\n",
      "Word: from, TF-IDF: 0.0000\n",
      "Word: is, TF-IDF: 0.0000\n",
      "Word: knowledge, TF-IDF: 0.0000\n",
      "Word: natural, TF-IDF: 0.0000\n",
      "Word: overlap, TF-IDF: 0.0000\n",
      "Word: science, TF-IDF: 0.0000\n",
      "Word: studies, TF-IDF: 0.4534\n",
      "Word: the, TF-IDF: 0.3575\n",
      "Word: to, TF-IDF: 0.0000\n",
      "Word: world, TF-IDF: 0.0000\n",
      "\n",
      "Document: Science is knowledge about the natural world\n",
      "Word: about, TF-IDF: 0.4446\n",
      "Word: aesthetics, TF-IDF: 0.0000\n",
      "Word: an, TF-IDF: 0.0000\n",
      "Word: and, TF-IDF: 0.0000\n",
      "Word: arts, TF-IDF: 0.0000\n",
      "Word: beauty, TF-IDF: 0.0000\n",
      "Word: between, TF-IDF: 0.0000\n",
      "Word: data, TF-IDF: 0.0000\n",
      "Word: extract, TF-IDF: 0.0000\n",
      "Word: from, TF-IDF: 0.0000\n",
      "Word: is, TF-IDF: 0.2838\n",
      "Word: knowledge, TF-IDF: 0.3506\n",
      "Word: natural, TF-IDF: 0.4446\n",
      "Word: overlap, TF-IDF: 0.0000\n",
      "Word: science, TF-IDF: 0.2838\n",
      "Word: studies, TF-IDF: 0.0000\n",
      "Word: the, TF-IDF: 0.3506\n",
      "Word: to, TF-IDF: 0.0000\n",
      "Word: world, TF-IDF: 0.4446\n",
      "\n",
      "Document: Data Science is a science to extract knowledge from data\n",
      "Word: about, TF-IDF: 0.0000\n",
      "Word: aesthetics, TF-IDF: 0.0000\n",
      "Word: an, TF-IDF: 0.0000\n",
      "Word: and, TF-IDF: 0.0000\n",
      "Word: arts, TF-IDF: 0.0000\n",
      "Word: beauty, TF-IDF: 0.0000\n",
      "Word: between, TF-IDF: 0.0000\n",
      "Word: data, TF-IDF: 0.5525\n",
      "Word: extract, TF-IDF: 0.3504\n",
      "Word: from, TF-IDF: 0.3504\n",
      "Word: is, TF-IDF: 0.2237\n",
      "Word: knowledge, TF-IDF: 0.2763\n",
      "Word: natural, TF-IDF: 0.0000\n",
      "Word: overlap, TF-IDF: 0.0000\n",
      "Word: science, TF-IDF: 0.4473\n",
      "Word: studies, TF-IDF: 0.0000\n",
      "Word: the, TF-IDF: 0.0000\n",
      "Word: to, TF-IDF: 0.3504\n",
      "Word: world, TF-IDF: 0.0000\n",
      "\n"
     ]
    }
   ],
   "source": [
    "from sklearn.feature_extraction.text import TfidfVectorizer\n",
    "\n",
    "# 示例文档集\n",
    "documents = [\n",
    "    'Data Science is an overlap between Arts and Science',\n",
    "    'Arts studies the beauty and aesthetics',\n",
    "    'Science is knowledge about the natural world',\n",
    "    'Data Science is a science to extract knowledge from data'\n",
    "]\n",
    "\n",
    "# 初始化TfidfVectorizer\n",
    "vectorizer = TfidfVectorizer()\n",
    "\n",
    "# 计算TF-IDF\n",
    "tfidf = vectorizer.fit_transform(documents)\n",
    "\n",
    "# 获取词语的IDF值\n",
    "idf = vectorizer.idf_\n",
    "\n",
    "# 获取词语的TF-IDF值\n",
    "feature_names = vectorizer.get_feature_names_out()\n",
    "tfidf_values = tfidf.toarray()\n",
    "\n",
    "# 打印词语的TF-IDF值\n",
    "for doc_id, doc in enumerate(documents):\n",
    "    print(f\"Document: {doc}\")\n",
    "    for word, val in zip(feature_names, tfidf_values[doc_id]):\n",
    "        print(f\"Word: {word}, TF-IDF: {val:.4f}\")\n",
    "    print()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "040e37ef-7412-4e24-8334-a3b3947b4ad5",
   "metadata": {},
   "source": [
    "这段代码首先创建了一个文档集，然后使用`TfidfVectorizer`来计算每个词的TF-IDF值。`fit_transform`方法既学习了 IDF，也返回了每个文档的词袋表示。然后，我们遍历每个文档并打印出每个词的TF-IDF值。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e3571168-dacf-4099-8548-86ce5027f45a",
   "metadata": {},
   "source": [
    "### 3. Word Embedding (Word2Vec)  \n",
    "Word2Vec是一种流行的词嵌入方法，它可以学习到单词的密集表示，使得语义上相似的单词在向量空间中距离较近。\n",
    "以下是使用Python中的gensim库来实现Word2Vec的示例代码：首先导入所需的库，然后定义了一个简单的文本数据集。接下来，我们使用NLTK库对文本进行分词，并使用gensim库中的Word2Vec类构建Word2Vec模型。在模型构建完成后，我们可以使用most_similar方法找到与指定单词最相似的单词，并使用wv属性获取单词的向量表示。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "0a3f6a73-f615-4966-bf15-82ec0dd40502",
   "metadata": {},
   "outputs": [
    {
     "ename": "ModuleNotFoundError",
     "evalue": "No module named 'gensim'",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mModuleNotFoundError\u001b[0m                       Traceback (most recent call last)",
      "Cell \u001b[0;32mIn[6], line 2\u001b[0m\n\u001b[1;32m      1\u001b[0m \u001b[38;5;66;03m# 导入所需的库\u001b[39;00m\n\u001b[0;32m----> 2\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mgensim\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mmodels\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m Word2Vec\n\u001b[1;32m      3\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mnltk\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mtokenize\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m word_tokenize\n\u001b[1;32m      5\u001b[0m \u001b[38;5;66;03m# 示例文本数据\u001b[39;00m\n",
      "\u001b[0;31mModuleNotFoundError\u001b[0m: No module named 'gensim'"
     ]
    }
   ],
   "source": [
    "# 导入所需的库\n",
    "from gensim.models import Word2Vec\n",
    "from nltk.tokenize import word_tokenize\n",
    "\n",
    "# 示例文本数据\n",
    "text_data = [\n",
    "    \"Word embeddings are dense vector representations of words.\",\n",
    "    \"These vectors are learned in such a way that words with similar meanings have similar representations.\",\n",
    "    \"Word2Vec is a popular technique used for generating word embeddings.\",\n",
    "    \"It uses a shallow neural network to learn word representations from large text corpora.\"\n",
    "]\n",
    "\n",
    "# 对文本数据进行分词\n",
    "tokenized_text = [word_tokenize(sentence.lower()) for sentence in text_data]\n",
    "\n",
    "# 构建Word2Vec模型\n",
    "model = Word2Vec(sentences=tokenized_text, vector_size=100, window=5, min_count=1, workers=4)\n",
    "\n",
    "# 查找与指定单词最相似的单词\n",
    "similar_words = model.wv.most_similar(\"word\", topn=5)\n",
    "print(\"Words similar to 'word':\", similar_words)\n",
    "\n",
    "# 获取单词的向量表示\n",
    "word_vector = model.wv[\"word\"]\n",
    "print(\"Vector representation of 'word':\", word_vector)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "082b9348-9b95-46fa-81ec-47aa5a0e66fd",
   "metadata": {},
   "source": [
    "### 4. Sentence Embedding (BERT)  \n",
    "BERT是一种基于Transformer的模型，它可以生成句子或段落的嵌入表示，这些表示能够捕捉到句子的上下文信息。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "d097d096-ff15-4b4e-b4a0-974545f4a3c1",
   "metadata": {},
   "outputs": [
    {
     "ename": "ModuleNotFoundError",
     "evalue": "No module named 'transformers'",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mModuleNotFoundError\u001b[0m                       Traceback (most recent call last)",
      "Cell \u001b[0;32mIn[7], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mtransformers\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m BertModel, BertTokenizer\n\u001b[1;32m      3\u001b[0m \u001b[38;5;66;03m# 初始化BERT模型和分词器\u001b[39;00m\n\u001b[1;32m      4\u001b[0m tokenizer \u001b[38;5;241m=\u001b[39m BertTokenizer\u001b[38;5;241m.\u001b[39mfrom_pretrained(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mbert-base-uncased\u001b[39m\u001b[38;5;124m'\u001b[39m)\n",
      "\u001b[0;31mModuleNotFoundError\u001b[0m: No module named 'transformers'"
     ]
    }
   ],
   "source": [
    "from transformers import BertModel, BertTokenizer\n",
    "\n",
    "# 初始化BERT模型和分词器\n",
    "tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')\n",
    "model = BertModel.from_pretrained('bert-base-uncased')\n",
    "\n",
    "# 待嵌入的句子\n",
    "sentence = \"Hello, how are you?\"\n",
    "\n",
    "# 对句子进行编码\n",
    "inputs = tokenizer(sentence, return_tensors=\"pt\")\n",
    "\n",
    "# 获取句子的嵌入表示\n",
    "outputs = model(**inputs)\n",
    "sentence_embedding = outputs.last_hidden_state.mean(dim=1).squeeze()\n",
    "print(\"Sentence embedding:\", sentence_embedding)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8afbdedb-114e-483a-ac31-0c8914c405d3",
   "metadata": {},
   "source": [
    "### 5. Graph Embedding (Node2Vec)\n",
    "Node2Vec是一种用于图数据的嵌入方法，它可以学习图中节点的嵌入表示，使得结构上相似的节点在向量空间中距离较近。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "c6c604b1-7404-445f-953c-9c4c7b74d8c8",
   "metadata": {},
   "outputs": [
    {
     "ename": "ModuleNotFoundError",
     "evalue": "No module named 'node2vec'",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mModuleNotFoundError\u001b[0m                       Traceback (most recent call last)",
      "Cell \u001b[0;32mIn[8], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mnode2vec\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m Node2Vec\n\u001b[1;32m      3\u001b[0m \u001b[38;5;66;03m# 创建一个简单的图结构\u001b[39;00m\n\u001b[1;32m      4\u001b[0m G \u001b[38;5;241m=\u001b[39m nx\u001b[38;5;241m.\u001b[39mread_edgelist(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mtest.edgelist\u001b[39m\u001b[38;5;124m\"\u001b[39m, create_using\u001b[38;5;241m=\u001b[39mnx\u001b[38;5;241m.\u001b[39mGraph(), nodetype\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mint\u001b[39m)\n",
      "\u001b[0;31mModuleNotFoundError\u001b[0m: No module named 'node2vec'"
     ]
    }
   ],
   "source": [
    "from node2vec import Node2Vec\n",
    "\n",
    "# 创建一个简单的图结构\n",
    "G = nx.read_edgelist(\"test.edgelist\", create_using=nx.Graph(), nodetype=int)\n",
    "\n",
    "# 初始化Node2Vec模型\n",
    "node2vec = Node2Vec(G, dimensions=64, walk_length=30, num_walks=200, workers=4)\n",
    "\n",
    "# 训练模型\n",
    "model = node2vec.fit(window=10, min_count=1, batch_words=4)\n",
    "\n",
    "# 获取节点的嵌入向量\n",
    "node_vector = model.wv['node1']\n",
    "print(\"Node 'node1' embedding:\", node_vector)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "34646836-dc88-4945-b252-75396566d5e2",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
